﻿// ARSys.Connector.h

#pragma once

using namespace System;
using namespace System::Collections::Generic;
using namespace System::Runtime::InteropServices; // for class Marshal
using namespace System::Text;


namespace ARSys { namespace Connector {


#pragma unmanaged

	//std::map<int, ARControlStruct> gTokens;
	int gCounter = 0;
	#define MAX_COUNT 40960

	int InitInternal(const char* szServer, const char* szUser, const char* szPwd)
	{
		ARStatusList status;
		ARControlStruct control;

		memset(&control, '\0', sizeof(control));

		control.cacheId          = 0;
		control.sessionId        = 0;
		strcpy(control.user,     szUser);
		strcpy(control.password, szPwd);
		strcpy(control.server,   szServer);
		strcpy(control.localeInfo.charSet, "UTF-8");	


		/* Remedy Startup */
		if (ARInitialization(&control, &status) >= 
			AR_RETURN_ERROR) {
				return -1;
		}
		FreeARStatusList(&status, FALSE);

		/* Verify user/password/server */
		if ( ARVerifyUser(&control, NULL,NULL,NULL,&status) >=
			AR_RETURN_ERROR) {
				return -2;
		}
		FreeARStatusList(&status, FALSE);


		// 是否需要考虑异步问题？
		if(gCounter++ > MAX_COUNT)
			gCounter = 0;
		gTokens[gCounter] = control;

		return gCounter;
	}

	void CleanupInternal(int token)
	{
		ARStatusList status;
		ARControlStruct control = gTokens[token];

		/* Terminate */
		ARTermination(&control, &status);
		FreeARStatusList(&status, FALSE);
	}
	void ReleaseARFieldValueList(ARFieldValueList* fieldList)
	{
		if(!fieldList)
			return;

		for(int i=0; i<fieldList->numItems; i++)
		{
			switch(fieldList->fieldValueList[i].value.dataType)
			{
			case AR_DATA_TYPE_CHAR:
				{
					delete[] fieldList->fieldValueList[i].value.u.charVal;
				}
				break;
			case AR_DATA_TYPE_DIARY:
				{
					delete[] fieldList->fieldValueList[i].value.u.diaryVal;
				}
				break;
			default:
				//throw gcnew NotImplementedException(L"代码只实现了AR_DATA_TYPE_INTEGER和AR_DATA_TYPE_CHAR类型，其他类型请自行添加");
				break;
			}
		}

		delete[] fieldList->fieldValueList;
	}

#pragma managed
	public ref class ARSysProxy : public IDisposable
	{
	private:
		ref class ARField;

	public:
		ARSysProxy(String^ server, String^ user, String^ password)
			: token(0), disposed(false)
		{
			const char* szServer = (char*)(void*)Marshal::StringToHGlobalAnsi(server);
			const char* szUser = (char*)(void*)Marshal::StringToHGlobalAnsi(user);
			const char* szPwd = (char*)(void*)Marshal::StringToHGlobalAnsi(password);
			
			token = InitInternal(szServer, szUser, szPwd);

			Marshal::FreeHGlobal((System::IntPtr)(void*)szServer);
			Marshal::FreeHGlobal((System::IntPtr)(void*)szUser);
			Marshal::FreeHGlobal((System::IntPtr)(void*)szPwd);

			if (token == -1)
				throw gcnew System::ApplicationException(L"ARInitialization failed.");
			else if (token == -2)
				throw gcnew System::Security::Authentication::InvalidCredentialException(L"Bad user or password.");
		}

	protected:
		~ARSysProxy()
		{
			// Dispose of managed resources.

			this->!ARSysProxy();
			disposed = true;
		}
	protected:
		!ARSysProxy()
		{
			// Dispose of unmanaged resources.
			if(token >= 0)
				CleanupInternal(token);
		}
	public:
		void CreateEntry(Object^ arobj)
		{
			if (arobj == nullptr)
				throw gcnew ArgumentNullException("arobj");

			String^ form = ((ARFormAttribute^)(arobj->GetType()->GetCustomAttributes(ARFormAttribute::typeid, false)[0]))->Name;

			const char* szForm = (char*)(void*)Marshal::StringToHGlobalAnsi(form);

			auto fieldList = BuildFieldList(arobj, true);
			auto convertedFieldList = ConvertFields(fieldList);

			ARControlStruct control = gTokens[token];
			ARStatusList status;
			AREntryIdType entryId;

			try
			{
				int hr = ARCreateEntry(&control, (char*)szForm, convertedFieldList.get(), entryId, &status);
				if (hr >= AR_RETURN_ERROR) 
				{
					ARUtil::ThrowARException(L"ARCreateEntry", hr, &status);
				}
			}
			finally
			{
				FreeARStatusList(&status, FALSE);
				Marshal::FreeHGlobal((System::IntPtr)(void*)szForm);
				ReleaseARFieldValueList(convertedFieldList.get());
			}
		}
	public:
		void SetEntry(Object^ arobj)
		{
			if (arobj == nullptr)
				throw gcnew ArgumentNullException("arobj");

			String^ form = ((ARFormAttribute^)(arobj->GetType()->GetCustomAttributes(ARFormAttribute::typeid, false)[0]))->Name;
			String^ entryId = FindEntryId(arobj);

			if(entryId == nullptr)
				throw gcnew ArgumentNullException("arobj->entryId");

			const char* szForm = (char*)(void*)Marshal::StringToHGlobalAnsi(form);
			const char* szEntryId = (char*)(void*)Marshal::StringToHGlobalAnsi(entryId);

			auto fieldList = BuildFieldList(arobj, false);
			auto convertedFieldList = ConvertFields(fieldList);

			ARControlStruct control = gTokens[token];
			ARStatusList status;
			AREntryIdList entryIds;
			AREntryIdType eId;
			strcpy(eId, szEntryId);

			entryIds.numItems = 1;
			entryIds.entryIdList = &eId;

			try
			{
				int hr = ARSetEntry(&control, (char*)szForm, &entryIds, convertedFieldList.get(), 0, AR_JOIN_SETOPTION_NONE, &status);
				if (hr >= AR_RETURN_ERROR) 
				{
					ARUtil::ThrowARException(L"ARSetEntry", hr, &status);
				}
			}
			finally
			{
				FreeARStatusList(&status, FALSE);
				Marshal::FreeHGlobal((System::IntPtr)(void*)szForm);
				Marshal::FreeHGlobal((System::IntPtr)(void*)szEntryId);
				ReleaseARFieldValueList(convertedFieldList.get());
			}
		}
	public:
		void DeleteEntry(Object^ arobj)
		{
			if (arobj == nullptr)
				throw gcnew ArgumentNullException("arobj");

			String^ form = ((ARFormAttribute^)(arobj->GetType()->GetCustomAttributes(ARFormAttribute::typeid, false)[0]))->Name;
			String^ entryId = FindEntryId(arobj);

			if(entryId == nullptr)
				throw gcnew ArgumentNullException("arobj->entryId");

			const char* szForm = (char*)(void*)Marshal::StringToHGlobalAnsi(form);
			const char* szEntryId = (char*)(void*)Marshal::StringToHGlobalAnsi(entryId);

			ARControlStruct control = gTokens[token];
			ARStatusList status;
			AREntryIdList entryIds;
			AREntryIdType eId;
			strcpy(eId, szEntryId);

			entryIds.numItems = 1;
			entryIds.entryIdList = &eId;

			try
			{
				int hr = ARDeleteEntry(&control, (char*)szForm, &entryIds, AR_JOIN_SETOPTION_NONE, &status);
				if (hr >= AR_RETURN_ERROR)
				{
					ARUtil::ThrowARException(L"ARDeleteEntry", hr, &status);
				}
			}
			finally
			{
				FreeARStatusList(&status, FALSE);
				Marshal::FreeHGlobal((System::IntPtr)(void*)szForm);
				Marshal::FreeHGlobal((System::IntPtr)(void*)szEntryId);
			}
		}
	public:
		ARTransaction^ BeginTransaction()
		{
			return gcnew ARTransaction(token);
		}

		std::unique_ptr<ARFieldValueList> ConvertFields(List<ARField^>^ fields)
		{
			std::unique_ptr<ARFieldValueList> list(new ARFieldValueList());

			list->numItems = fields->Count;
			list->fieldValueList = new ARFieldValueStruct[fields->Count];

			int i = 0;
			for each(ARField^ field in fields)
			{
				auto& pField = list->fieldValueList[i];

				pField.fieldId = field->ARInternalId;
				pField.value.dataType = field->DataType;

				switch(field->DataType)
				{
				case AR_DATA_TYPE_CHAR:
					{
						pField.value.u.charVal = ARUtil::GetUTF8Text((String^)field->Value);
					}
					break;
				case AR_DATA_TYPE_DIARY:
					{
						pField.value.u.diaryVal = ARUtil::GetUTF8Text((String^)field->Value);
					}
					break;
				case AR_DATA_TYPE_INTEGER:
					{
						pField.value.u.intVal = (int)field->Value;
					}
					break;
				case AR_DATA_TYPE_DATE:
				default:
					throw gcnew NotImplementedException(L"代码只实现了AR_DATA_TYPE_INTEGER和AR_DATA_TYPE_CHAR类型，其他类型请自行添加");
				}
				
				i++;
			}

			return list;
		}

		List<ARField^>^ BuildFieldList(Object^ arobj, bool newEntity)
		{
			auto pair = gcnew List<ARField^>();

			for each (auto prop in arobj->GetType()->GetProperties())
			{
				auto arFields = prop->GetCustomAttributes(ARFieldAttribute::typeid, false);
				if (arFields->Length == 1)
				{
					auto arField = (ARFieldAttribute^)arFields[0];		

					if(newEntity || (!newEntity && arField->AllowModify))
					{
						auto field = gcnew ARField();

						field->ARInternalId = arField->Id;
						field->DataType = arField->Type;
						field->Value = prop->GetValue(arobj, nullptr);
						
						// truncate the string...
						if(dynamic_cast<String^>(field->Value) != nullptr
							&& arField->MaxLength != 0)
						{
							TruncateString(field, arField->MaxLength);
						}

						pair->Add(field);
					}
				}
			}

			return pair;
		}

	private:
		void TruncateString(ARField^ field, int maxLength)
		{
			auto value = (String^)(field->Value);

			int len = value->Length;
			auto text = value->ToCharArray();

			while(Encoding::UTF8->GetByteCount(text, 0, len) > maxLength)
			{
				len--;
			}
			
			if(len != value->Length)
				field->Value = value->Substring(0, len);
		}

	private:
		String^ FindEntryId(Object^ arobj)
		{
			for each(auto prop in arobj->GetType()->GetProperties())
			{
				if(prop->GetCustomAttributes(AREntryIdAttribute::typeid, false)->Length == 1)
					return (String^)prop->GetValue(arobj, nullptr);
			}

			throw gcnew Exception("arobj should have at least one property that is decorated with AREntryIdAttribute.");
		}

	private:
		bool disposed;
		int token;

#define C_AR_DATA_TYPE(t) literal int C_##t = t;

	public:
		C_AR_DATA_TYPE(AR_DATA_TYPE_NULL)
		C_AR_DATA_TYPE(AR_DATA_TYPE_KEYWORD)
		C_AR_DATA_TYPE(AR_DATA_TYPE_INTEGER)
		C_AR_DATA_TYPE(AR_DATA_TYPE_REAL)
		C_AR_DATA_TYPE(AR_DATA_TYPE_CHAR)
		C_AR_DATA_TYPE(AR_DATA_TYPE_DIARY)
		C_AR_DATA_TYPE(AR_DATA_TYPE_ENUM)
		C_AR_DATA_TYPE(AR_DATA_TYPE_TIME)
		C_AR_DATA_TYPE(AR_DATA_TYPE_BITMASK)
		C_AR_DATA_TYPE(AR_DATA_TYPE_BYTES)
		C_AR_DATA_TYPE(AR_DATA_TYPE_DECIMAL)
		C_AR_DATA_TYPE(AR_DATA_TYPE_ATTACH)
		C_AR_DATA_TYPE(AR_DATA_TYPE_CURRENCY)
		C_AR_DATA_TYPE(AR_DATA_TYPE_DATE)
		C_AR_DATA_TYPE(AR_DATA_TYPE_TIME_OF_DAY)
		C_AR_DATA_TYPE(AR_MAX_STD_DATA_TYPE)
		C_AR_DATA_TYPE(AR_DATA_TYPE_JOIN)
		C_AR_DATA_TYPE(AR_DATA_TYPE_TRIM)
		C_AR_DATA_TYPE(AR_DATA_TYPE_CONTROL)
		C_AR_DATA_TYPE(AR_DATA_TYPE_TABLE)
		C_AR_DATA_TYPE(AR_DATA_TYPE_COLUMN)
		C_AR_DATA_TYPE(AR_DATA_TYPE_PAGE)
		C_AR_DATA_TYPE(AR_DATA_TYPE_PAGE_HOLDER)
		C_AR_DATA_TYPE(AR_DATA_TYPE_ATTACH_POOL)
		C_AR_DATA_TYPE(AR_DATA_TYPE_ULONG)
		C_AR_DATA_TYPE(AR_DATA_TYPE_COORDS)
		C_AR_DATA_TYPE(AR_DATA_TYPE_VIEW)
		C_AR_DATA_TYPE(AR_DATA_TYPE_DISPLAY)
		C_AR_DATA_TYPE(AR_DATA_TYPE_MAX_TYPE)



	private:
		ref class ARField
		{
		public:
			property int ARInternalId;
			property int DataType;
			property Object^ Value;
		};
	};




}}
